Summary of my #laser12 lectures in less than 144 characters: LINQ == monads, async await == comonands. As simple as that.
—- Erik Meijer from twitter
越來越覺得自己寫不出什麼東西,今天讓我來划水一下。接續前一篇的結果,不知道大家是否覺得很困惑,至少對我來說是這樣,只不過是寫了一個剛剛好跟Linq同名的擴充方法,為什麼就可以直接套用Query-Expression呢?這真是太莫名其妙了!!!!
其實這完全不違反微軟的規格,文件上有說明,只要符合以下語意的方法,就可以為任意的型別支援查詢運算式
class C
{
public C<T> Cast<T>();
}
class C<T> : C
{
public C<T> Where(Func<T,bool> predicate);
public C<U> Select<U>(Func<T,U> selector);
public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
Func<T,U,V> resultSelector);
// ...
}
// 因為太長了,完整的部份放到文章最後
這裡面最大的重點在於C<T>
這個型別,可是視為兩個泛型組成,分別是代表容器的C
與內容物的T
,而我們熟悉的Linq就是以IEnumerable
去實做C
,只要符合容器與內容物的組合,就可以經過加工使用查詢運算式!
容器與內容物的組合,是不是好像似曾相識?就是一直以來提到的monad阿!不要懷疑,就連Linq的開發者Erik Meijer都直接告訴我們Linq就是monads了,另外Erik Meijer也說了
every Linq function could actually be implemented by SelectMany
其實一開始Linq的設計就是針對FP的實現,並且留下了很大的彈性讓使用者可以擴充。
舉例來說,在使用webAPI時,我們經常會設計一個型別去承接回傳的資料:
public class Response<T>
{
public string Status { get; set; }
public T Data { get; set; }
public string Error { get; set; }
}
注意看這個型別,就滿足了C<T>
的特徵,也就是說我可以幫它寫擴充方法
static class ResponseExtension
{
public static R Match<T, R>(this Response<T> source, Func<T, R> f)
=> source switch
{
{ Status: "Success", Data: T d } => f(d),
{ Status: "Fail", Error: var e } => throw new Exception(e)
};
}
如此一來就可以使用到各種Linq的好處,是不是很神奇呢?
當初看到這種Query-Expression的用法讓我驚嘆不已,然而找到解答時更是大吃一驚,Query-Expression的設計真的是醍醐灌頂,其實Monad的觀念在C#中真的無處不在,回過頭來看真的有種萬物皆monad的感覺,接下來的篇幅就拿來以FP的風格來解釋型別或者介紹一些函數式風格的套件吧。
delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);
class C
{
public C<T> Cast<T>();
}
class C<T> : C
{
public C<T> Where(Func<T,bool> predicate);
public C<U> Select<U>(Func<T,U> selector);
public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
Func<T,U,V> resultSelector);
public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,U,V> resultSelector);
public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector);
public O<T> OrderBy<K>(Func<T,K> keySelector);
public O<T> OrderByDescending<K>(Func<T,K> keySelector);
public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector);
public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
Func<T,E> elementSelector);
}
class O<T> : C<T>
{
public O<T> ThenBy<K>(Func<T,K> keySelector);
public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}
class G<K,T> : C<T>
{
public K Key { get; }
}